﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using Newtonsoft.Json.Linq;
using BitcoinMarket.DataSources;
using System.Threading;
using System.Collections.Generic;

namespace BitcoinMarket.DataConnectors
{
    public class BlockChainDataConnector
    {
        private Action<Collection<BlockChainChartsDataItem>> HashRateReadyCallback = null;
        private Action<Collection<BlockChainChartsDataItem>> MarketsPriceReadyCallback = null;
        private Action<Collection<BlockChainChartsDataItem>> AvgConfirmTimeReadyCallback = null;

        private Action<Collection<BlockChainChartItem>> UserSelectedDataReadyCallback = null;

        private JArray HashRateData = null;
        private JArray MarketsPriceData = null;
        private JArray AvgConfirmTimeData = null;

        private JArray[] DownloadedData = null;

        private List<string> chartTitles = new List<string>();
        private List<string> chartDescriptions = new List<string>();
        private List<string> chartLabels = new List<string>();
        private List<string> chartUrls = new List<string>();
        private List<string> chartKeyNames = new List<string>();

        public void GetUserSelectedData(Action<Collection<BlockChainChartItem>> readyCallback)
        {
            this.GetUserSelectedData(readyCallback, "");
        }

        public void GetUserSelectedData(Action<Collection<BlockChainChartItem>> readyCallback, string key)
        {
            //Set the callback
            this.UserSelectedDataReadyCallback = readyCallback;

            //Get all the necessary data from settings (URLs, Chart title, Axes titles, Chart description)
            AppNetworkStatusChartSettings settings = new AppNetworkStatusChartSettings();
            Collection<NetworkStatusChartSettingsItem> chartList = settings.GetAllNetworkInfoItems();

            if (chartList.Count > 0)
            {
                foreach (NetworkStatusChartSettingsItem chartItem in chartList)
                {
                    if ((key == "" || key == chartItem.keyName) && settings.GetValueOrDefault<bool>(chartItem.keyName, chartItem.enabledDefault)) // check if the chart is enabled by the user
                    {
                        this.chartLabels.Add(chartItem.chartYAxisLabel);
                        this.chartTitles.Add(chartItem.chartTitle);
                        this.chartUrls.Add(chartItem.publicUrl);
                        this.chartKeyNames.Add(chartItem.keyName);
                        this.chartDescriptions.Add(chartItem.chartDescription);
                    }
                }

                //Load data from BlockChain
                BlockChainDataSource BlockChainSource = new BlockChainDataSource();

                if (this.chartUrls.Count > 0)
                {
                    BlockChainSource.GetData(this.chartUrls, BlockChainUserSelectedDataDownloaded);
                }
                else
                {
                    this.BlockChainUserSelectedDataDownloaded(null);
                }
            }
            else
            {
                this.BlockChainUserSelectedDataDownloaded(null);
            }
        }

        /// <summary>
        /// Callback for Hash Rate data download
        /// Saves the downloaded data to private variables and starts the parser
        /// </summary>
        /// <param name="HashRateData"></param>
        /// <param name="PricesData"></param>
        public void BlockChainUserSelectedDataDownloaded(JArray[] Data)
        {
            this.DownloadedData = Data;

            ThreadPool.QueueUserWorkItem(new WaitCallback(BlockChainParseData));
        }

        /// <summary>
        /// Parses the downloaded data for BlockChain
        /// </summary>
        /// <param name="o"></param>
        private void BlockChainParseData(object o)
        {
            //Prepare the Markets data
            Collection<Collection<BlockChainChartsDataItem>> AllItems = new Collection<Collection<BlockChainChartsDataItem>>();

            if (this.DownloadedData != null)
            {
                foreach (JArray dataArray in this.DownloadedData)
                {
                    if (dataArray != null)
                    {
                        Collection<BlockChainChartsDataItem> Items = new Collection<BlockChainChartsDataItem>();

                        foreach (JToken item in dataArray)
                        {
                            DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0);
                            date = date.AddSeconds(Double.Parse(item["x"].ToString())).ToLocalTime();

                            double val = (double)item["y"];

                            Items.Add(new BlockChainChartsDataItem() { Date = date, ShortDate = date.ToShortDateString(), Value = val });
                        }

                        AllItems.Add(Items);
                    }
                }
            }

            //Pass the data for postprocessing
            this.postprocessDownloadedData(AllItems);
        }

        /// <summary>
        /// Adds aditional information to the parsed data as Chart Title and Axis Title and calls the callback
        /// </summary>
        /// <param name="data">Data to postprocess</param>
        private void postprocessDownloadedData(Collection<Collection<BlockChainChartsDataItem>> Data)
        {
            Collection<BlockChainChartItem> FullData = new Collection<BlockChainChartItem>();

            if (Data != null)
            {
                int i = 0;

                foreach (Collection<BlockChainChartsDataItem> Item in Data)
                {
                    FullData.Add(new BlockChainChartItem { Data = Item, KeyName = this.chartKeyNames[i], ChartTitle = this.chartTitles[i], YAxistitle = this.chartLabels[i], ChartDescription = this.chartDescriptions[i] });
                    i++;
                }
            }


            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                this.UserSelectedDataReadyCallback(FullData);
            });
        }

        /// <summary>
        /// Obtains the Hash Rate data from the BlockChain data source
        /// </summary>
        /// <param name="readyCallback">Callback to pass the obtained data</param>
        public void GetHashRateData(Action<Collection<BlockChainChartsDataItem>> readyCallback)
        {
            //Set the callback
            this.HashRateReadyCallback = readyCallback;

            //Load data from BlockChain
            BlockChainDataSource BlockChainSource = new BlockChainDataSource();
            BlockChainSource.GetHashRateData(this.BlockChainHashRateDataDownloaded);
        }

        /// <summary>
        /// Callback for Hash Rate data download
        /// Saves the downloaded data to private variables and starts the parser
        /// </summary>
        /// <param name="HashRateData"></param>
        /// <param name="PricesData"></param>
        public void BlockChainHashRateDataDownloaded(JArray Data)
        {
            this.HashRateData = Data;

            ThreadPool.QueueUserWorkItem(new WaitCallback(BlockChainParseHashRateData));
        }

        /// <summary>
        /// Parses the downloaded data for BlockChain
        /// </summary>
        /// <param name="o"></param>
        private void BlockChainParseHashRateData(object o)
        {
            //Prepare the Markets data
            Collection<BlockChainChartsDataItem> Items = new Collection<BlockChainChartsDataItem>();

            foreach (JToken item in this.HashRateData)
            {
                DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0);
                date = date.AddSeconds(Double.Parse(item["x"].ToString())).ToLocalTime();

                double val = (double)item["y"];

                Items.Add(new BlockChainChartsDataItem() { Date = date, ShortDate = date.ToShortDateString(), Value = val });
            }

            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                this.HashRateReadyCallback(Items);
            });
        }



        /// <summary>
        /// Obtains the Markets Price data from the BlockChain data source
        /// </summary>
        /// <param name="readyCallback">Callback to pass the obtained data</param>
        public void GetMarketsPriceData(Action<Collection<BlockChainChartsDataItem>> readyCallback)
        {
            //Set the callback
            this.MarketsPriceReadyCallback = readyCallback;

            //Load data from BlockChain
            BlockChainDataSource BlockChainSource = new BlockChainDataSource();
            BlockChainSource.GetMarketsPriceData(this.BlockChainMarketsPriceDataDownloaded);
        }

        /// <summary>
        /// Callback for Hash Rate data download
        /// Saves the downloaded data to private variables and starts the parser
        /// </summary>
        /// <param name="HashRateData"></param>
        /// <param name="PricesData"></param>
        public void BlockChainMarketsPriceDataDownloaded(JArray Data)
        {
            this.MarketsPriceData = Data;

            ThreadPool.QueueUserWorkItem(new WaitCallback(BlockChainParseMarketsPriceData));
        }

        /// <summary>
        /// Parses the downloaded data for BlockChain
        /// </summary>
        /// <param name="o"></param>
        private void BlockChainParseMarketsPriceData(object o)
        {
            //Prepare the Markets data
            Collection<BlockChainChartsDataItem> Items = new Collection<BlockChainChartsDataItem>();

            foreach (JToken item in this.MarketsPriceData)
            {
                DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0);
                date = date.AddSeconds(Double.Parse(item["x"].ToString())).ToLocalTime();

                double val = (double)item["y"];

                Items.Add(new BlockChainChartsDataItem() { Date = date, ShortDate = date.ToShortDateString(), Value = val });
            }

            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                this.MarketsPriceReadyCallback(Items);
            });
        }



        /// <summary>
        /// Obtains the Avg Confirm Time data from the BlockChain data source
        /// </summary>
        /// <param name="readyCallback">Callback to pass the obtained data</param>
        public void GetAvgConfirmTimeData(Action<Collection<BlockChainChartsDataItem>> readyCallback)
        {
            //Set the callback
            this.AvgConfirmTimeReadyCallback = readyCallback;

            //Load data from BlockChain
            BlockChainDataSource BlockChainSource = new BlockChainDataSource();
            BlockChainSource.GetAvgConfirmTimeData(this.BlockChainAvgConfirmTimeDataDownloaded);
        }

        /// <summary>
        /// Callback for Hash Rate data download
        /// Saves the downloaded data to private variables and starts the parser
        /// </summary>
        /// <param name="HashRateData"></param>
        /// <param name="PricesData"></param>
        public void BlockChainAvgConfirmTimeDataDownloaded(JArray Data)
        {
            this.AvgConfirmTimeData = Data;

            ThreadPool.QueueUserWorkItem(new WaitCallback(BlockChainParseAvgConfirmTimeData));
        }

        /// <summary>
        /// Parses the downloaded data for BlockChain
        /// </summary>
        /// <param name="o"></param>
        private void BlockChainParseAvgConfirmTimeData(object o)
        {
            //Prepare the Markets data
            Collection<BlockChainChartsDataItem> Items = new Collection<BlockChainChartsDataItem>();

            foreach (JToken item in this.AvgConfirmTimeData)
            {
                DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0);
                date = date.AddSeconds(Double.Parse(item["x"].ToString())).ToLocalTime();

                double val = (double)item["y"];

                Items.Add(new BlockChainChartsDataItem() { Date = date, ShortDate = date.ToShortDateString(), Value = val });
            }

            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                this.AvgConfirmTimeReadyCallback(Items);
            });
        }
    }

    /// <summary>
    /// Class for storing BlockChain data item
    /// </summary>
    /// <example>
    /// [{ 
	///		"x" : 1290602498, //Unix timestamp
	///		"y" : 1309696.2116000003
	///	}]
    /// </example>
    public class BlockChainChartsDataItem
    {
        public DateTime Date { get; set; }
        public string ShortDate { get; set; }
        public Double Value { get; set; }
    }

    /// <summary>
    /// Data item containing all the data needed for one BlockChain chart
    /// </summary>
    public class BlockChainChartItem
    {
        public string KeyName { get; set; }
        public string ChartTitle { get; set; }
        public string YAxistitle { get; set; }
        public string ChartDescription { get; set; }
        public Collection<BlockChainChartsDataItem> Data { get; set; }
        
        public double Max
        {
            get
            {
                double max = this.Data[0].Value;

                foreach (BlockChainChartsDataItem item in this.Data)
                {
                    if (max < item.Value)
                    {
                        max = item.Value;
                    }
                }

                return Math.Ceiling(max);
            }
        }
        
        public double Min
        {
            get
            {
                double min = this.Data[0].Value;

                foreach (BlockChainChartsDataItem item in this.Data)
                {
                    if (min > item.Value)
                    {
                        min = item.Value;
                    }
                }

                return Math.Floor(min);
            }
        }
    }
}
